package com.retrofit.server.processor;


import com.okhttp.annotations.ApiGenerateService;
import com.okhttp.annotations.ApiResponseHandler;
import com.retrofit.server.processor.AnnotationClassMethod.MethodAnnotation;
import com.retrofit.server.processor.AnnotationClassMethod.MethodReturn;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

/**
 * 接口类
 */
public class AnnotationClass {

  static final String HANDLER_NAME="BaseResponseHandler";
  static final String HANDLER_NAME2="BaseResponseBodyHandler";
  static final String KIND_VOID="void";
  static final String KIND_RETROFIT2_CALLBACK="retrofit2.Callback";

  private final Elements elementUtils;

  private final TypeElement annotatedClassElement;
  private final String annotatedPkgName;
  private final String annotatedSimpleName;

  private TypeMirror classHandlerTypeMirror;

  private List<AnnotationClassMethod> methodList;

  public AnnotationClass(Elements elementUtils, Types typeUtils, TypeElement typeElement)
      throws ProcessorException {
    this.elementUtils=elementUtils;
    this.annotatedClassElement = typeElement;
    //获取当前package , class name:
    PackageElement pkgElement = elementUtils.getPackageOf(typeElement);
    annotatedPkgName = pkgElement.isUnnamed() ? null : pkgElement.getQualifiedName().toString();
    annotatedSimpleName = typeElement.getSimpleName().toString();
    //ClassDoc
    //获取注解value
    ApiGenerateService annntationClass = this.annotatedClassElement
        .getAnnotation(ApiGenerateService.class);
    //注解中有class 类型value时，直接取值容易报MirroredTypeException 异常
    try {
      Class<?> rhClass = annntationClass.value();
      TypeElement annotationClassTypeName = elementUtils.getTypeElement(rhClass.getName());
      classHandlerTypeMirror=annotationClassTypeName.asType();
    } catch (MirroredTypeException mirtype) {
      classHandlerTypeMirror=mirtype.getTypeMirror();
    }

    //处理类中所包含的方法：
    classMethodProcessor();

  }

  public String getAnnotatedPkgName() {
    return annotatedPkgName;
  }

  public String getAnnotatedSimpleName() {
    return annotatedSimpleName;
  }

  public TypeMirror getClassHandlerTypeMirror() {
    return classHandlerTypeMirror;
  }

  public void setClassHandlerTypeMirror(TypeMirror classHandlerTypeMirror) {
    this.classHandlerTypeMirror = classHandlerTypeMirror;
  }

  public List<AnnotationClassMethod> getMethodList() {
    return methodList;
  }

  /**
   * 方法处理
   */
  private void classMethodProcessor() {
    //过滤掉父类方法
    List<ExecutableElement> executableElements = ElementFilter.methodsIn(annotatedClassElement.getEnclosedElements());
    //
    if (executableElements.size() < 1) {
      return;
    }
    Print.log("typeParameterElement : " , executableElements.toString());
    methodList = new ArrayList<>(executableElements.size());
    //解析出当前类所有接口方法：
    for (ExecutableElement etElement : executableElements) {
      Print.log("ExecutableElement:" , etElement.toString());
      //接口方法定义：
      AnnotationClassMethod classMethod = new AnnotationClassMethod();
      //方法注解集 @BindResponseHandler
      boolean isGenerate=this.processAnnotationMethod(etElement,classMethod);
      if(!isGenerate){
        //不生成此方法的实现
        continue;
      }
      CodeBlock.Builder javaDocBuilder=CodeBlock.builder();
      //方法名称
      classMethod.setMethodName(etElement.getSimpleName().toString());
      javaDocBuilder.add("\n")
          .add("{@link $N.$N#$N}$W\n",annotatedPkgName,annotatedSimpleName,classMethod.getMethodName());
      //方法参数：
      this.processMethodParams(classMethod,etElement.getParameters(),javaDocBuilder);
      //方法返回类型处理
      this.processMethodReturn(etElement,classMethod);

      classMethod.setCodeBlockJavadoc(javaDocBuilder.build());
      methodList.add(classMethod);
    }
  }

  /**
   * 处理方法的参数
   * @param classMethod
   * @param parameters
   * @return
   */
  private void processMethodParams(AnnotationClassMethod classMethod,List<? extends VariableElement> parameters,CodeBlock.Builder javaDocBuilder){
    StringBuilder methodParamSb =new StringBuilder("");

    for (VariableElement parameter : parameters) {
      String paramName = parameter.getSimpleName().toString();
      TypeMirror typeMirror = parameter.asType();
      AnnotationClassMethod.MethodParam methodParam =
          new AnnotationClassMethod.MethodParam(paramName, typeMirror);
      classMethod.addParams(methodParam);
      methodParamSb.append(paramName).append(",");
      javaDocBuilder.add("@param $N \n",paramName);
    }
    String methodParamStr = "";
    if ((methodParamSb.lastIndexOf(",")) != -1) {
      methodParamStr = methodParamSb.substring(0, methodParamSb.length() - 1);
    }
    Print.log("methodParamName: ", methodParamStr);
    classMethod.setMethodParamNames(methodParamStr);
  }


  /**
   * 处理方法中注解 @BindResponseHandler
   * @param etElement
   * @return true:生成此方法实现，false:不生成此方法实现
   */
  private boolean processAnnotationMethod(ExecutableElement etElement,AnnotationClassMethod classMethod){
    ApiResponseHandler annotationhandler = etElement.getAnnotation(ApiResponseHandler.class);
    boolean isGenerate=true;
    if(annotationhandler!=null){
      TypeMirror typeMirrorHandler;
      try{
        Class<?> handlerClass=annotationhandler.handler();
        typeMirrorHandler=elementUtils.getTypeElement(handlerClass.getName()).asType();
      }catch (MirroredTypeException mirtype){
        typeMirrorHandler=mirtype.getTypeMirror();
      }catch (Exception e){
        typeMirrorHandler=null;
      }
      TypeMirror typeMirrorGenericity;
      try{
        Class<?> genericityClass=annotationhandler.genericity();
        typeMirrorGenericity=elementUtils.getTypeElement(genericityClass.getName()).asType();
      }catch (MirroredTypeException mirtype){
        typeMirrorGenericity=mirtype.getTypeMirror();
      }catch (Exception e){
        typeMirrorGenericity=null;
      }
      isGenerate=annotationhandler.isGenerate();
      boolean isSync=annotationhandler.isSync();
      //返回参数不能空
      if(typeMirrorHandler!=null && typeMirrorHandler.getKind()!=TypeKind.VOID){
        classMethod.setMethodAnnotation(new MethodAnnotation(typeMirrorHandler,typeMirrorGenericity,isGenerate,isSync));
      }
    }
    return isGenerate;
  }

  /**
   * 处理方法返回类型，生成方法回调参数及代码块内容:<br>
   * 1、解析出返回值的类型：优先方法中注解类型，如果方法注解无值则使用类注解的值<br>
   * 2、根据注解泛型类型或返回值数据类型来添加回调参数泛型类型<br>
   * 3、添加新生成方法的返回值并添加泛型类型<br>
   * 4、添加调用代码块<br>
   * 5、如果为同步执行则需要返回Response<T>
   * @param etElement
   * @param classMethod
   */
  private void processMethodReturn(ExecutableElement etElement,AnnotationClassMethod classMethod){
    //代码块
    CodeBlock.Builder codeBuilder=CodeBlock.builder();
    //接口方法返回值，带泛型，并生成回调参数
    TypeMirror tmReturnType = etElement.getReturnType();
    //获取回调泛型类型
    if (!(tmReturnType instanceof NoType
        || tmReturnType.getKind() == TypeKind.VOID
        || tmReturnType.getKind() == TypeKind.NONE)) {
      Print.log("returnType : "+tmReturnType.toString());
      
      //取返回值中的泛型类型
      if (tmReturnType instanceof DeclaredType) {
        //DeclaredType declaredType = (DeclaredType) tmReturnType;
        TypeElement handlerElement;
        ClassName cbClassName;
        ParameterizedTypeName parameterizedTypeName;
        String handlerName;
        String handlerSuperName="";
        //优先使用方法中的注解@BindResponseHandler： handler，genericity,isGenertae
        MethodAnnotation methodAnnotation= classMethod.getMethodAnnotation();
        if(methodAnnotation!=null){
          if(methodAnnotation.isSync){
            //同步方法：
            boolean isT;
            try{
              //使用call的返回类型做的方法的返回值
              TypeMirror returnGenerice = ((DeclaredType) tmReturnType).getTypeArguments().get(0);
              ClassName cln=ClassName.get("retrofit2","Response");
              ParameterizedTypeName ptn=  ParameterizedTypeName.get(cln, TypeName.get(returnGenerice));
              classMethod.setMethodReturn(new MethodReturn(ptn,""));
              isT=true;
            }catch (Exception e){
              //异常时使用泛型 T
              classMethod.setMethodReturn(new MethodReturn(TypeVariableName.get("T"),"T"));
              isT=false;
            }
            generateSyncCode(codeBuilder,tmReturnType, classMethod.getMethodName(),classMethod.getMethodParamNames(),true,isT);
            classMethod.setCodeBlock(codeBuilder.build());
            return ;
          }
          TypeMirror typeMirrorHandler=methodAnnotation.typeMirrorHandler;
          handlerElement = (TypeElement) ((DeclaredType) typeMirrorHandler).asElement();
          handlerName=handlerElement.getSimpleName().toString();
          if(KIND_VOID.equals(StringUtils.toLowerStr(handlerName))){
            //如果方法注解中@handler为Void,则使用类注解的值
            typeMirrorHandler=classHandlerTypeMirror;
            handlerElement=(TypeElement) ((DeclaredType) typeMirrorHandler).asElement();
            handlerName=handlerElement.getSimpleName().toString();
          }
          cbClassName= (ClassName) ClassName.get(typeMirrorHandler);
        }else{
          //无@BindResponseHandler 注解,使用 classHandlerTypeMirror;
          handlerElement=(TypeElement) ((DeclaredType) classHandlerTypeMirror).asElement();
          handlerName=handlerElement.getSimpleName().toString();
          cbClassName= (ClassName) ClassName.get(classHandlerTypeMirror);
        }
        
        // hander super class name
        if(handlerElement.getKind()== ElementKind.CLASS){
          handlerSuperName=((DeclaredType)handlerElement.getSuperclass()).asElement().getSimpleName().toString();
        }else if(handlerElement.getKind()==ElementKind.INTERFACE){
          //callback interface
          handlerSuperName="";
        }
        
        Print.log("handlerName -- superName :",handlerName,handlerSuperName);
        
        //添加what
        codeBuilder.add("int what=generateWhat()+handler.hashCode();\n");
        //根据 BaseResponseBodyHandler ,BaseResponseHandler,CallBack 进行组装
        if(HANDLER_NAME.equals(handlerName)
            || HANDLER_NAME.equals(handlerSuperName)){
          //BaseResponseHandler
          codeBuilder.add("handler.onBeforeExecute(what);\n");
          this.generateBaseResponseHandler(classMethod, tmReturnType, cbClassName, methodAnnotation);
        }else if(HANDLER_NAME2.equals(handlerName)
            || HANDLER_NAME2.equals(handlerSuperName)){
          //BaseResponseBodyHandler
          codeBuilder.add("handler.onBeforeExecute(what);\n");
          this.generateBaseResponseBodyHandler(classMethod, cbClassName, methodAnnotation);
        }else {
          //其他使用 Callback 处理方式
          generateOtherHandler(classMethod, tmReturnType, methodAnnotation, cbClassName);
        }
        //添加代码块，生成 Call
        codeBuilder.add("$T call = innerCall().$N($N);$W\n", tmReturnType, classMethod.getMethodName(), classMethod.getMethodParamNames());
        //调用执行，传入回调参数
        codeBuilder.add("call.enqueue($N);$W\n", "handler");
        codeBuilder.add("return  what;\n");
        //添加返回值类型
        MethodReturn methodReturn=classMethod.getMethodReturn();
        if(methodReturn!=null){
          methodReturn.typeName=TypeName.get(int.class);
        }else {
          methodReturn=new MethodReturn(int.class,"");
          classMethod.setMethodReturn(methodReturn);
        }
      }else{
        //添加代码块，生成 Call
        generateSyncCode(codeBuilder,tmReturnType, classMethod.getMethodName(),classMethod.getMethodParamNames(),false,false);
//        codeBuilder.add("$T call = innerCall().$N($N);$W\n", tmReturnType, classMethod.getMethodName(), classMethod.getMethodParamNames());
//        codeBuilder.add("try {\n")
//            .add("$W call.execute();$W")
//            .add("\n} catch (IOException e) {")
//            .add("\ncom.okhttp.utils.RetrofitLog.e(\"$N$N\",e.getMessage(),e); \n}",annotatedSimpleName,"Service");
      }
      classMethod.setCodeBlock(codeBuilder.build());
    }
  }
  
  private void generateSyncCode(CodeBlock.Builder builder,TypeMirror tmReturnType,
    String methodName,String methodParamName,boolean isReturn,boolean isT){
    //添加代码块，生成 Call
    builder.add("$T call = innerCall().$N($N);$W\n", tmReturnType,methodName,methodParamName);
    builder.add("try {\n")
      .add(isT?isReturn?"$W return  call.execute();$W":"$W call.execute();$W":isReturn?"$W return (T) call.execute();$W":"$W call.execute();$W")
      .add("\n} catch (Exception e) {")
      .add("\ncom.okhttp.utils.RetrofitLog.e(\"$N$N\",e.getMessage(),e); \n}\n",annotatedSimpleName,"Service");
      if(isReturn){
        builder.add("return null;");
      }
  }
  
  /**
   * 其他方式
   * @param classMethod
   * @param tmReturnType
   * @param methodAnnotation
   * @param cbClassName
   */
  private void generateOtherHandler(AnnotationClassMethod classMethod, TypeMirror tmReturnType,
      MethodAnnotation methodAnnotation, ClassName cbClassName) {
    //
    TypeName[] parameterizedTypeNames=null;
    if(methodAnnotation!=null){
      String genericityname=((DeclaredType)methodAnnotation.typeMirrorGenericity).asElement().getSimpleName().toString();
      if(!KIND_VOID.equals(StringUtils.toLowerStr(genericityname))
          //retrofit2.Callback 只能添加个泛型，为call的泛型
          && !KIND_RETROFIT2_CALLBACK.equals(cbClassName.reflectionName())
        ){
        //其他定义的类
        parameterizedTypeNames= generateGenericParam(tmReturnType,1, methodAnnotation.typeMirrorGenericity);
      }
    }
    if(parameterizedTypeNames==null){
      parameterizedTypeNames= generateGenericParam(tmReturnType,0, null);
    }
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(cbClassName,parameterizedTypeNames);
    //将回调添加参数中：
    AnnotationClassMethod.MethodHandlerParam methodHandlerParam =
        new AnnotationClassMethod.MethodHandlerParam("handler",cbClassName, parameterizedTypeName);
    classMethod.setMethodHandlerParam(methodHandlerParam);
  }

  private void generateBaseResponseBodyHandler(AnnotationClassMethod classMethod,
      ClassName cbClassName, MethodAnnotation methodAnnotation) {
    //public static <T> void getIndexPost(String test, ResultBodyHandler<T> handler) {
    // handler.onBeforeExecute();
    //Call<ResponseBody> call = innerCall().getIndexPost(test);
    //call.enqueue(handler);
    //}
    TypeName parameterizedTypeNames=null;
    if(methodAnnotation!=null){
      String genericityname=((DeclaredType)methodAnnotation.typeMirrorGenericity).asElement().getSimpleName().toString();
      if(!KIND_VOID.equals(StringUtils.toLowerStr(genericityname))){
        parameterizedTypeNames=TypeName.get(methodAnnotation.typeMirrorGenericity);
      }
    }
    //有泛型时，则需要在返回值前添加泛型
    if(parameterizedTypeNames==null){
      parameterizedTypeNames= TypeVariableName.get("T");
      //添加返回值 ，在返回数据前添加泛型
      classMethod.setMethodReturn(new MethodReturn(void.class,"T"));
    }
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(cbClassName,parameterizedTypeNames);
    AnnotationClassMethod.MethodHandlerParam methodHandlerParam =
        new AnnotationClassMethod.MethodHandlerParam("handler",cbClassName, parameterizedTypeName);
    classMethod.setMethodHandlerParam(methodHandlerParam);
  }

  private void generateBaseResponseHandler(AnnotationClassMethod classMethod,
      TypeMirror tmReturnType, ClassName cbClassName, MethodAnnotation methodAnnotation) {
    //public static <T> void doGet2(String test, BaseResponseHandler<T,Result<DataBean>> handler) {
    //handler.onBeforeExecute();
    //Call<Result<DataBean>> call = innerCall().doGet2(test);
    //call.enqueue(handler);
    //}
    TypeName[] parameterizedTypeNames=null;

    if(methodAnnotation!=null){
      TypeElement element = (TypeElement) ((DeclaredType) methodAnnotation.typeMirrorHandler).asElement();
      List<? extends TypeParameterElement> typeParameters = element.getTypeParameters();
      //Handler 只有一个泛型时，按自定义的泛型生成
      if(typeParameters!=null && typeParameters.size()==1){
        Print.log(typeParameters.toString());
        parameterizedTypeNames=new TypeName[]{TypeName.get(methodAnnotation.typeMirrorGenericity)};
      }else{
        String genericityname=((DeclaredType)methodAnnotation.typeMirrorGenericity).asElement().getSimpleName().toString();
        if(!KIND_VOID.equals(StringUtils.toLowerStr(genericityname))){
          parameterizedTypeNames= generateGenericParam(tmReturnType,1,methodAnnotation.typeMirrorGenericity);
        }
      }
    }
    //为Null或Void
    if(parameterizedTypeNames==null){
      parameterizedTypeNames= generateGenericParam(tmReturnType,1, null);
      //添加返回值 ，在返回数据前添加泛型
      classMethod.setMethodReturn(new MethodReturn(void.class,"T"));
    }

    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(cbClassName,parameterizedTypeNames);
    AnnotationClassMethod.MethodHandlerParam methodHandlerParam =
        new AnnotationClassMethod.MethodHandlerParam("handler",cbClassName, parameterizedTypeName);
    classMethod.setMethodHandlerParam(methodHandlerParam);
  }


  /**
   * 生成参数的泛型类型
   * @param declaredType
   * @param size ==1:则添加一个T泛型
   * @param typeMirrorGenericity 不为空则做泛型使用
   * @return
   */
  private TypeName[] generateGenericParam(TypeMirror declaredType, int size, TypeMirror typeMirrorGenericity){
    List<? extends TypeMirror> typeArguments = ((DeclaredType)declaredType).getTypeArguments();
    TypeName[] parameterizedTypeNames=new TypeName[typeArguments.size()+size];
    int k=0;
    if(size==1){
      if(typeMirrorGenericity!=null){
        parameterizedTypeNames[k++]=TypeName.get(typeMirrorGenericity);
      }else{
        parameterizedTypeNames[k++]=TypeVariableName.get("T");
      }
    }
    for (TypeMirror typeArgument : typeArguments) {
      parameterizedTypeNames[k++]=TypeName.get(typeArgument);
    }
    return parameterizedTypeNames;
  }



}
